home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 22 / Cream of the Crop 22.iso / program / ctlib100.zip / INSTALL.LZH / CONTAINR.TXT < prev    next >
Text File  |  1996-10-12  |  21KB  |  335 lines

  1.                         CHAPTER 2
  2.                  The base container object
  3.  
  4.  
  5. This chapter describes the complete interface of TContainer, the starting point of the Containers Library object hierarchy.  All containers are ultimately derived from TContainer, so the topics discussed in this chapter apply to all containers in the library, regardless of how they structure the data, what type of data they work with or where is the data stored.
  6.  
  7. In this chapter, you will learn how to:
  8.  
  9.   - Insert and delete data in a container
  10.   - Use iterative methods for performing an action on all or
  11.     some of the items in a container
  12.   - Use the Status field of a container
  13.   - Use the Error method to handle errors
  14.   - Write data-structure independent code
  15.  
  16.  
  17. Adding data to a container
  18.  
  19. Adding data to a container is one of the most common operations that you will perform on a container.  Usually, once you create a new container, you need to start adding data to it right away. Or you may find that you need to add new data to a previously created container.  TContainer provides a very simple way of doing this:  the Insert method.
  20.  
  21. Insert is a boolean function that will take a pointer parameter, and return True or False depending on the success of the operation.  For example, the following code will insert 3000 items into the MyContainer object:
  22.  
  23.   var
  24.     Item : Pointer;
  25.     MyContainer : TContainer;
  26.  
  27.   begin
  28.     for i := 1 to 3000 do
  29.     begin
  30.       Item := NewItem;
  31.       MyContainer^.Insert(Item);
  32.     end;
  33.   end;
  34.  
  35. The code in the example above can be used to insert data into any container in the library.  Since all containers descend from TContainer, it does not matter if MyContainer is an array, a linked list, a table or even a B+ tree.  As long as Item is of the proper data type, the application does not need to know what container is being used to store the data.  This also applies to all other methods in the TContainer object.
  36.  
  37. The process of inserting new data into a container is always the same.  First, you need to create the data item that you want to insert.  Depending on the type of container you are using, the data item can be dynamically allocated data stored in the heap, or static data stored in a variable or a constant.  After the data item is created, all you need to do is call the Insert method of the container you are using.
  38.  
  39. The following example shows how to insert a static record into a  standard array container:
  40.  
  41.   var
  42.     MyRec : TMyRec;
  43.     MyArray : TStdArray;
  44.  
  45.   begin
  46.     MyRec.LastName := 'Smith';
  47.     MyRec.FirstName := 'John';
  48.     MyArray^.Insert(@MyRec);
  49.   end;
  50.  
  51. What gets stored in a container depends on the type of container that you are using.  Some containers will just store the value of the pointer passed as a parameter of the Insert method while some others will copy the actual data item to another location.  In the example above, when the record in is inserted into the array, the container will make copy of the data in MyRec and store it in another location in memory.  This way, MyRec can be discarded when it is not needed anymore (for example, if MyRec is a variable declared within a routine, it will be automatically "disposed of" when you exit the routine).
  52.  
  53. For more information on how specific containers work, refer to the chapters included in Part II of this manual.
  54.  
  55.  
  56. Deleting data from a container
  57.  
  58. TContainer provides several ways of deleting data from a container.  You can delete individual items or delete all items in a container with just one call to a container method.  You can also use methods to dispose of the items after they are deleted from the container.
  59.  
  60.  
  61. Deleting individual items
  62.  
  63. You can delete individual items from a container by using the Delete method.  Delete takes a pointer to the item that you want to delete from the container, searches for the item in the container and then deletes it.
  64.  
  65.   MyCollection^.Delete(Item);
  66.   MyArray^.Delete(@MyRec);
  67.  
  68. The way Delete finds the correct item to delete depends on how the container stores the data inserted into it.  For example, in containers that store dynamically allocated data and always keep the data in memory (like collections, binary trees and linked lists), Delete compares the value of Item (which is a pointer to the actual item) with the memory address of items in the container.  If the value of Item matches the memory address of one item in the container, Delete proceeds to delete the item.  However, in containers that do not store dynamically allocated data or that keep portions of the data in a stream, Delete compares the data in Item with items stored in the container, until a match is found.
  69.  
  70. Delete does not dispose of Item after it has been deleted from the container.  Therefore, you should always dispose of Item after you are done working with it.  If you want to keep your code data-structure independent, you should always use FreeItem for this purpose, instead of deleting the item directly.  
  71.  
  72. If you need to both delete and dispose of the item, you must use Free instead, which is discussed later in this section.
  73.  
  74.  
  75. Deleting all items
  76.  
  77. Sometimes, you may need to delete all the items in a container at once, instead of deleting items one at a time.  This is the purpose of the DeleteAll method.  DeleteAll deletes all items in a container, but does not dispose of them.  It does not take any parameters.
  78.  
  79.   MyList^.DeleteAll;
  80.  
  81. You must be careful when using this method since once you delete the items from a container, you cannot access them through the container again.  If you do not keep track of the deleted items, you will not be able to dispose of them later resulting in a memory leak.
  82.  
  83. This method is useful if you want to move the items from one container to another, or if you stored items in a container only for temporary structured access to them and you do not wish to dispose of the items when you dispose of the container.  For example, you might want to move the items from one sorted collection to another sorted collection that uses a different key to sort the items.  This is how it would be done:
  84.  
  85.   for i := 0 to Pred(MyCollectByID^.Count) do
  86.   begin 
  87.     Item := MyCollectByID^.At(i);
  88.     MyCollectByName^.Insert(Item);
  89.   end;
  90.   MyCollectByID^.DeleteAll;
  91.   Dispose(MyCollectByID, Done);
  92.  
  93. Or you might want to store in a collection pointers to all desktop windows in a Turbo Vision application, for displaying their titles in a listbox:
  94.  
  95.   if Desktop^.Current <> nil then
  96.     begin
  97.       Curr := PWindow(Desktop^.First);
  98.       repeat
  99.       MyWinCollection^.Insert(Curr);
  100.         Curr := PWindow(Curr^.Next);
  101.       until (Curr = PWindow(Desktop^.First));
  102.     end;
  103.   { ...do something with the windows... }
  104.   MyWinCollection^.DeleteAll;
  105.   Dispose(MyWinCollection, Done);
  106.  
  107. If you need to both delete and dispose of the items, you must use FreeAll instead, which is discussed later in this section.
  108.  
  109.  
  110. Deleting and disposing of individual items
  111.  
  112. You can easily delete individual items in a container and then have the container automatically dispose of them for you.  This is what the Free method does.  Free takes a pointer to the item that you want to delete, looks for the item in the container and deletes it.  After the item is delete, It calls FreeItem to dispose of it.  Free is equivalent to:
  113.  
  114.   Delete(Item);
  115.   FreeItem(Item);
  116.  
  117. Deleting and disposing of all items
  118. To delete all items in a container and then have the container dispose of them for you, you must use the FreeAll method.  FreeAll is a simple method that does not take any parameters.
  119.  
  120.   MyBTree^.FreeAll;
  121.   MyStack^.FreeAll;
  122.  
  123. The Count field
  124.  
  125. You can determine the number of elements currently stored in a container by using its Count field.  In most containers, Count will be equal to the number of items inserted into the container, minus the number of items deleted from it.  The value of Count will not depend on the size allocated for the container.  For example, you can allocate enough space for storing 20 items in a collection, but have only 10 stored in it.  Count, in this case, would have a value of 10.
  126.  
  127. However, in containers that have a fixed number of elements (like the standard array containers), Count will always have a fixed value equal to the maximum number of elements that can be stored in the container.  For example, if you create an array to store 20 elements, Count will always have a value of 20 whatever the number of elements that you insert or delete from the array. 
  128.  
  129.  
  130. Iterative methods
  131.  
  132. Often you will find yourself writing loops to go through all items in a container to display the data or perform some calculation.  Other times, you will want to perform an action on only items in the container that satisfy some search criterion.  For these purposes, TContainer provides two iterator methods:  ForEach and ForEachThat.  TContainer also provides two specialized iterator methods that allow you to conditionally delete data in a container:  DeleteAllThat and FreeAllThat.
  133.  
  134. Note    Additional iterator methods are implemented in TSequence and TGraph.  These methods include the FirstThat, LastThat, NextThat and PrevThat iterators.
  135.  
  136.  
  137. The ForEach iterator
  138.  
  139. ForEach takes a pointer to a far local procedure of type TActionProcedure.  The procedure has one parameter, which is a pointer to an item stored in the container.  ForEach calls that procedure once for each item in the container, in the order that items appear in the container.  The following PrintGlobalReport procedure shows an example of a ForEach iterator.
  140.  
  141.   procedure PrintGlobalReport;
  142.  
  143.     procedure PrintReportItem(Item: PMyObject); far;
  144.     begin
  145.       with Item do
  146.         {...print report item...}
  147.     end;
  148.  
  149.   begin
  150.     MyBinaryTree^.ForEach(@PrintReport);
  151.   end;
  152.  
  153. For each item in the container the nested procedure PrintReportItem is called.  PrintReportItem simply prints the item's information to the screen.
  154.  
  155. Important!    You need to be careful about what sort of procedures and functions you call with iterators.  In this example, PrintReportItem must be a procedure -- it cannot be an object's method -- and it must be local to (nested in the same block with) the routine that is calling it.  It must also be declared as a far procedure, either with the far directive or with the $F+ compiler directive.  Finally, the procedure must take a pointer to a container item as its only parameter.
  156.  
  157.  
  158. The ForEachThat iterator
  159.  
  160. ForEachThat is a specialized type of ForEach iterator that allows you to select the items you want to perform an action on, by using a search criterion.  ForEachThat takes two parameters:  a pointer to a far local boolean function (of type TTestFunction) that implements the search criterion and a pointer to a far local procedure that will perform an action on items that satisfy the search criterion.  For example:
  161.  
  162.   procedure PrintCongratulationNotes;
  163.  
  164.     function IncSales(Item : PEmployee): Boolean; far;
  165.     begin
  166.       with Item do
  167.         IncSales := (CurrSales > OldSales);
  168.     end;
  169.  
  170.     procedure PrintNote(Item : PEmployee); far;
  171.     begin
  172.       with Item do
  173.          {... print congratulation note ...}
  174.     end;
  175.  
  176.   begin
  177.     MyTable^.ForEachThat(@IncSales, @PrintNote);
  178.   end;
  179.  
  180. IncSales is a function that returns True if the item passed as a parameter satisfies the search criterion (in this case, those employees that have increased their sales level).  Again notice that IncSales and PrintNote are nested and use the far call model.
  181.  
  182.  
  183. Conditionally deleting data
  184.  
  185. TContainer allows you to conditionally delete and dispose of items in a container, by means of the DeleteAllThat and FreeAllThat iterators.  These iterators take one parameter, which is a pointer to a far local boolean function of type TTestFunction.
  186.  
  187.   procedure RetrieveExpired;
  188.  
  189.     function HasExpired(P : PAccount): Boolean; far;
  190.     begin
  191.      if P^.HasExpired
  192.        then begin
  193.               ExpiredCollection^.Insert(P);
  194.               HasExpired := True;
  195.             end
  196.        else HasExpired := False;
  197.     end;
  198.  
  199.   begin
  200.     MyCollection^.DeleteAllThat(@HasExpired);
  201.   end;
  202.  
  203. DeleteAllThat does not dispose of the items deleted.  If you need to both delete and dispose of the items, you must use FreeAllThat.  For example:
  204.  
  205.   procedure RemoveNonSubscriptors;
  206.  
  207.     function NotSubscribed(P: PClient): Boolean; far;
  208.     begin
  209.       NotSubscribed := not P^.IsSubscriptor;
  210.     end;
  211.  
  212.   begin
  213.     MyTable^.FreeAllThat(@NotSubscribed);
  214.   end;
  215.  
  216. When using DeleteAllThat, you must be careful to either dispose of each item deleted or to store it in another container that will keep track of the items deleted.  Otherwise, you will not be able to dispose of those items resulting in a memory leak.
  217.  
  218.  
  219. The status of a container
  220.  
  221. Occasionally, when working with containers, an error occurs or the container cannot complete an operation for some particular reason.  When this happens, you can determine the reason for this by checking the Status field of a container, which stores the error status of the container.  Possible values for this field are:
  222.  
  223.   Error Code    │ Value │  Meaning
  224.   ══════════════╪═══════╪══════════════════════════════════════
  225.   ctOk          │   0   │  No error has occurred
  226.   ctRange       │  -1   │  Requested index is out of range
  227.   ctOverflow    │  -2   │  Failed to expand the size of the
  228.                 │       │    container
  229.   ctInit        │  -3   │  Error occurred during initialization
  230.   ctStream      │  -4   │  Error on associated stream
  231.   ctOutOfMemory │  -5   │  No memory available to complete the
  232.                 │       │    operation
  233.   ctDuplicate   │  -6   │  Attempt to insert a duplicate key
  234.   ctInvalidSig  │  -7   │  Incorrect file signature
  235.   ctInvalidVer  │  -8   │  Unsupported file version
  236.  
  237.  
  238. Status is also used to store the return status of certain operations.
  239. The following are possible status flag values for this field:
  240.  
  241.   Status flag   │ Value │ Meaning
  242.   ══════════════╪═══════╪═════════════════════════════════════
  243.   ctTop         │   1   │  Read attempt after the last item 
  244.                 │       │    in the container
  245.   ctBottom      │   2   │  Read attempt before the first item
  246.                 │       │    in the container
  247.  
  248.  
  249. Status flags are mostly used to mark the end of a loop.  For example, the following code will display the data of all items in a sequential container:
  250.  
  251.   with MyContainer^ do
  252.   begin
  253.     if Status > ctOk { Reset an status flag }
  254.       then Status := ctOk;
  255.     Item := First(Index);
  256.     while Status = ctOk do
  257.     begin
  258.       DisplayData(Item);
  259.       Item := Next(Index);
  260.     end;
  261.   end;
  262.  
  263. If an error occurs (Status < 0) or the bottom of the container is reached (Status > 0), then the loop will end.
  264.  
  265.  
  266. Handling errors
  267.  
  268. All errors that occur in a container are handled by the Error method.  When an error occurs, the container in which the error occurred will call Error and then abort the operation.  Error has two parameters:  Code and Info.  Code is the numeric code of the error that occurred.  The Status field is set to the value passed in the Code parameter.  Info is an untyped parameter that can contain any additional information about the error.  Its type depends on the kind of error that occurred.  For example, if Error is called with a code of ctRange, Info will contain the index value that caused the out of range error.
  269.  
  270. The default Error method displays an error message if the library was compiled with the cdDebug conditional define (more information about conditional defines can be found in the Bsd.* help files).  You can override Error in descendant containers if you wish to handle errors differently or if you want to provide more detailed error messages for a given container.
  271.  
  272. Error can return any of the following values, which will indicate the calling method how to proceed after the error is handled:
  273.  
  274.   Error command │ Value │ Meaning
  275.   ══════════════╪═══════╪══════════════════════════════════
  276.   ctRetry       │   0   │  Retry the operation that caused 
  277.                 │       │    the error
  278.   ctAbort       │  -1   │  Abort the operation that caused 
  279.                 │       │    the error
  280.  
  281.  
  282. By default, Error returns ctAbort.  ctRetry is currently supported only by the file structures, which include the tables, the B trees and the B+ trees.
  283.  
  284. After an error has occurred in a container, all operations are suspended until Reset is called.  This method will reset any container error condition by setting the value of Status to ctOk and calling the Reset method of any stream used by the container.
  285.  
  286. Important!    If you override Error to support ctRetry, you should always call Reset before returning control to the calling routine.
  287.  
  288.  
  289. Packing a container
  290.  
  291. Packing a container is the process of recovering unused resources allocated for the container.  For example, when you delete an item from a table, the space used by the item will not be recovered until you pack a container.
  292.  
  293. Pack removes all nil and deleted items from a container and frees any memory, disk space or other type of data storage media associated with those items.  Not all containers fully support this method, however.  Containers like the linked lists or the binary trees automatically free unused resources when items are deleted, so the default Pack method in these containers does not perform any operation.
  294.  
  295. Zap is an additional method that you can use to free all items in a container and then pack the container.  Zap is equivalent to calling FreeAll followed by Pack.
  296.  
  297.  
  298. Writing data-structure independent code
  299.  
  300. Writing data-structure independent applications require the consistent use of a particular method in the container's interface:  DoneItem.  DoneItem is used to insure that data retrieved from any container is appropriately disposed of when it is no longer needed.
  301.  
  302. Some containers make a copy of the data when it is retrieved from the container (for example, containers that store the data in a stream) while others do not (e.g., containers that store all the data directly in the heap).  To make your code independent of the data structures that you are using, you should always call DoneItem after retrieving a data item from a container.  DoneItem will determine if the data is just a copy of the source data and decide what to do with it accordingly.  If the data item is just a copy made by the container, DoneItem will proceed to dispose of it.  Otherwise, DoneItem will do nothing with the data item and simply return control to the calling routine.
  303.  
  304. The following code can be used to display all the data items stored in any sequential container, regardless of where the container stores the data or how it handles it.
  305.  
  306.   with MyContainer^ do
  307.   begin
  308.     if Status > ctOk
  309.       then Status := ctOk;
  310.     Item := First(Index);
  311.     while Status = ctOk do
  312.     begin
  313.       DisplayItem(Item);
  314.       DoneItem(Item);
  315.       Item := Next(Index);
  316.     end;
  317.   end;
  318.  
  319. The call to DoneItem immediately after the item is displayed will ensure that Item is disposed of if it is just a copy of the data or that it is left untouched if it is a pointer to the original data held in memory.
  320.  
  321. DoneItem is always called immediately after a data item is inserted into a container.  Therefore, you should always insert an item into the container after you are done making changes to it, or retrieve it again from the container after inserting it.
  322.  
  323.   Client := New(PClient, Init('Smith', 'John'));
  324.   Client^.AccountType := 'Savings';
  325.   Client^.PhoneNumber := '(405) 122-2333';
  326.   MyStreamArray^.Insert(Client); 
  327.  
  328. The use of DoneItem is only required when writing data-structure independent applications, or when items in a container are stored in a stream.  You can choose not to use DoneItem when working with containers that store all the data directly in the heap.
  329.  
  330.  
  331. Containers and streams
  332.  
  333. Except file structures, which always store data in a permanent storage area, all containers can be read and stored from and to a stream, respetively, by means of the standard Load and Store methods.  For more information on streams and the Load and Store methods, refer to the chapter "Streams," on your Turbo Vision Programming Guide.
  334.  
  335. All containers use the GetItem and PutItem methods to read and store individual items from and to a stream, respectively.  In most cases, you will not have to worry about these methods since the container you use will already have all the necessary functionality implemented into it.  However, you will need to modify these methods if you want to store special types of data that are not currently supported (e.g. record types with pointer fields).  Consult the reference guide for additional information on the GetItem and PutItem methods.